#include <jni.h>
#include <android/asset_manager_jni.h>
#include <android/asset_manager.h>
#include <string>
#include <android/log.h>
#include <unistd.h>

#define LOG_TAG "Native"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)

#ifdef __cplusplus
extern "C" {
#endif

jobject getField(JNIEnv *env, const char *className, const char *fieldName, jobject obj) {
    jclass classCls = env->FindClass("java/lang/Class");
    jmethodID for_name = env->GetStaticMethodID(classCls, "forName",
                                                "(Ljava/lang/String;)Ljava/lang/Class;");
    jmethodID get_field_mid = env->GetMethodID(classCls, "getDeclaredField",
                                               "(Ljava/lang/String;)Ljava/lang/reflect/Field;");
    jstring clsName = env->NewStringUTF(className);
    jstring fName = env->NewStringUTF(fieldName);
    jobject clz = env->CallStaticObjectMethod(classCls, for_name, clsName);
    jobject field = env->CallObjectMethod(clz, get_field_mid, fName);
    jclass field_cls = env->GetObjectClass(field);
    jmethodID set_access = env->GetMethodID(field_cls, "setAccessible", "(Z)V");
    jboolean yes = 1;
    env->CallVoidMethod(field, set_access, yes);
    jmethodID field_get_mid = env->GetMethodID(field_cls, "get",
                                               "(Ljava/lang/Object;)Ljava/lang/Object;");
    jobject ret = env->CallObjectMethod(field, field_get_mid, obj);
    env->DeleteLocalRef(fName);
    env->DeleteLocalRef(clsName);
    return ret;
}

void setField(JNIEnv *env, const char *className,
              const char *fieldName, jobject obj, jobject obj1) {
    jclass classCls = env->FindClass("java/lang/Class");
    jmethodID for_name = env->GetStaticMethodID(classCls, "forName",
                                                "(Ljava/lang/String;)Ljava/lang/Class;");
    jmethodID get_field_mid = env->GetMethodID(classCls, "getDeclaredField",
                                               "(Ljava/lang/String;)Ljava/lang/reflect/Field;");
    jstring clsName = env->NewStringUTF(className);
    jstring fName = env->NewStringUTF(fieldName);
    jobject clz = env->CallStaticObjectMethod(classCls, for_name, clsName);
    jobject field = env->CallObjectMethod(clz, get_field_mid, fName);
    jclass field_cls = env->GetObjectClass(field);
    jmethodID set_access = env->GetMethodID(field_cls, "setAccessible", "(Z)V");
    jboolean yes = 1;
    env->CallVoidMethod(field, set_access, yes);
    jmethodID field_set_mid = env->GetMethodID(field_cls, "set",
                                               "(Ljava/lang/Object;Ljava/lang/Object;)V");
    env->CallVoidMethod(field, field_set_mid, obj, obj1);
    env->DeleteLocalRef(fName);
    env->DeleteLocalRef(clsName);
}

void copyFromAssets(AAssetManager *mgr, const char *name, const char *destFile) {
    AAsset *asset = AAssetManager_open(mgr, name, AASSET_MODE_UNKNOWN);
    long len = AAsset_getLength(asset);
    char *buff = (char *) malloc((size_t) (len + 1));
    buff[len] = 0;
    AAsset_read(asset, buff, (size_t) len);
    for (int i = 0; i < 1024 && i < len; i++) {
        buff[i] = (char) (buff[i] - 2);
    }
    FILE *dest = fopen(destFile, "wb");
    if (dest == nullptr) {
        LOGV("open write file failed!");
        return;
    }
    fwrite(buff, 1, (size_t) len, dest);
    fclose(dest);
    free(buff);
}

JNIEXPORT void JNICALL
Java_com_android_shellcpp_ShellApplication_nativeInit(
        JNIEnv *env, jobject instance, jobject context,
        jobject assetManager, jstring jar_, jstring dd_, jstring ll_) {

    // copy file
    const char *dd = env->GetStringUTFChars(dd_, 0);
    const char *ll = env->GetStringUTFChars(ll_, 0);
    const char *jar = env->GetStringUTFChars(jar_, 0);
    AAssetManager *mgr = AAssetManager_fromJava(env, assetManager);
    if (mgr == nullptr) {
        LOGV("AAssetManager is null");
        return;
    }

    char dexFile[100];
    sprintf(dexFile, "%s/%s", dd, jar);
    char tmp[100];
    sprintf(tmp, "%s/init_%s", dd, jar);
    if (access(tmp, F_OK) == 0) {
        LOGV("file is exist!");
    } else {
        LOGV("copy file!");
        /*
        jclass native_class = env->GetObjectClass(context);
        jmethodID pm_id = env->GetMethodID(
                native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
        jobject pm_obj = env->CallObjectMethod(context, pm_id);
        jclass pm_clazz = env->GetObjectClass(pm_obj);
        jmethodID package_info_id = env->GetMethodID(
                pm_clazz, "getPackageInfo",
                "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
        jmethodID mId = env->GetMethodID(native_class, "getPackageName", "()Ljava/lang/String;");
        jstring pkg_str = static_cast<jstring>(env->CallObjectMethod(context, mId));
        jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 0);
        jclass pi_clazz = env->GetObjectClass(pi_obj);
        jfieldID ver_id = env->GetFieldID(pi_clazz, "versionName", "Ljava/lang/String;");
        jstring ver = static_cast<jstring>(env->GetObjectField(pi_obj, ver_id));
        jboolean isCopy;
        const char *vc = env->GetStringUTFChars(ver, &isCopy);
        sprintf(tmp, "%s%s", vc, jar);
        env->ReleaseStringUTFChars(ver, vc);
        */

        sprintf(tmp, "%s", jar);
        copyFromAssets(mgr, tmp, dexFile);

        sprintf(tmp, "%s/init_%s", dd, jar);
        FILE *init = fopen(tmp, "w");
        fwrite("init", 1, 4, init);
        fclose(init);
    }

    // set class loader
    jclass cxt_cls = env->GetObjectClass(context);
    jmethodID gcl_mid = env->GetMethodID(cxt_cls, "getClassLoader", "()Ljava/lang/ClassLoader;");
    jobject pathClassLoader = env->CallObjectMethod(context, gcl_mid);
    LOGV("getClassLoader");
    jclass dexClassLoaderCls = env->FindClass("dalvik/system/DexClassLoader");
    jmethodID dexClassCreator = env->GetMethodID(
            dexClassLoaderCls, "<init>",
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
    LOGV("new DexClassLoader 1");
    jstring dfs = env->NewStringUTF(dexFile);
    jobject dexClassLoader = env->NewObject(dexClassLoaderCls, dexClassCreator, dfs, dd_, ll_,
                                            pathClassLoader);
    env->DeleteLocalRef(dfs);
    LOGV("new DexClassLoader 2");
    jfieldID pathList_fid = env->GetFieldID(dexClassLoaderCls, "pathList",
                                            "Ldalvik/system/DexPathList;");
    LOGV("pathList");
    jobject p_path = env->GetObjectField(pathClassLoader, pathList_fid);
    jobject d_path = env->GetObjectField(dexClassLoader, pathList_fid);
    LOGV("dexElements");
    jobject p_arr = getField(env, "dalvik.system.DexPathList", "dexElements", p_path);
    jobject d_arr = getField(env, "dalvik.system.DexPathList", "dexElements", d_path);
    LOGV("Array");
    jclass arr_cls = env->FindClass("java/lang/reflect/Array");
    jmethodID get_len_mid = env->GetStaticMethodID(arr_cls, "getLength", "(Ljava/lang/Object;)I");
    jmethodID new_arr_mid = env->GetStaticMethodID(arr_cls, "newInstance",
                                                   "(Ljava/lang/Class;I)Ljava/lang/Object;");
    jmethodID arr_get = env->GetStaticMethodID(arr_cls, "get",
                                               "(Ljava/lang/Object;I)Ljava/lang/Object;");
    jmethodID arr_set = env->GetStaticMethodID(arr_cls, "set",
                                               "(Ljava/lang/Object;ILjava/lang/Object;)V");
    jint i = env->CallStaticIntMethod(arr_cls, get_len_mid, p_arr);
    jint j = env->CallStaticIntMethod(arr_cls, get_len_mid, d_arr);
    jclass d_arr_cls = env->GetObjectClass(d_arr);
    jmethodID get_class = env->GetMethodID(d_arr_cls, "getClass", "()Ljava/lang/Class;");
    jclass cls_cls = env->FindClass("java/lang/Class");
    jmethodID get_component_mid = env->GetMethodID(
            cls_cls, "getComponentType", "()Ljava/lang/Class;");
    jobject arr_cls_cls = env->CallObjectMethod(d_arr, get_class);
    jobject arr_type = env->CallObjectMethod(arr_cls_cls, get_component_mid);
    jobject n_arr = env->CallStaticObjectMethod(arr_cls, new_arr_mid, arr_type, i + j);
    for (jint k = 0; k < i + j; k++) {
        if (k < j) {
            jobject e = env->CallStaticObjectMethod(arr_cls, arr_get, d_arr, k);
            env->CallStaticVoidMethod(arr_cls, arr_set, n_arr, k, e);
        } else {
            jobject e = env->CallStaticObjectMethod(arr_cls, arr_get, p_arr, k - j);
            env->CallStaticVoidMethod(arr_cls, arr_set, n_arr, k, e);
        }
    }
    setField(env, "dalvik.system.DexPathList", "dexElements", p_path, n_arr);
    env->ReleaseStringUTFChars(dd_, dd);
    env->ReleaseStringUTFChars(ll_, ll);
    env->ReleaseStringUTFChars(jar_, jar);
}

#ifdef __cplusplus
}
#endif
